package SSF.OS.NetFlow;

import java.util.*;

/**
 * User defined HashMap.
 * It can shrink as well as grow dynamically according to the usage 
 * of memory
 */

//Detail:
//   It "uses" a HashMap with LoadFactor 1, and two factors: lowerLoadFactor
// upperLoadFactor are used to actually monitor the memory usage.The LoadFactor
// is 1 > upperLoadFactor means the rehash() function of HashMap will never 
// be called. Instead two methods "shrinkRehash" and "growRehash" are used to 
// adjust the memory allocated to this table. By this way, the user would have
// total control of memory allocatoin of this table. In another word, there is
// no rehash() caused by "HashMap.put()" method.
//   A variable "capacity" is used to record the size of the memory allocated.
// when it grow, capacity = capacity * 2 + 1, when it shrink, capacity = 
// (capacity - 1) / 2,  or capacity = capacity / 2;
//
//   I was planning to write this as an extension of HashMap, but it's not
// doable because "this" cannot be the left value, so I cannot resize the map
// easily.

public class ShrinkableHashMap
{
    /////////////////// member variables /////////////////

    /* if the utilization is smaller than this, shrink the map
     * the default is 0.25
     */
    private float lowerLoadFactor = (float)0.25;

    /* if the utilization is bigger than this, grow larger
     * the default is 0.75
     */
    private float upperLoadFactor = (float)0.75;

    /* capacity of this map */
    private int capacity;

    /** the real map used */
    private HashMap hashMap;

    private int LOWEST_SIZE = 41;
    /////////////////// methods //////////////////////////

    //---------------------------------- Constructors
    /**
     * this default constructor should NOT be used
     */
    public ShrinkableHashMap() {
	hashMap = new HashMap(101);
	capacity = 101;
	System.err.println("Warning: the ShrinkableHashMap is initialized with no parameter");
    }

    /**
     * Constructor: the LoadFactor is always set to be 1.0
     * specify the intial capacity
     */
    public ShrinkableHashMap(int initialCapacity) {
	hashMap = new HashMap(initialCapacity, (float)1.0);
	capacity = initialCapacity;

	if (initialCapacity > LOWEST_SIZE)
	    LOWEST_SIZE = initialCapacity;
    }

    /**
     * Constructor: 
     * specify the upperFactor and the initCapacity
     */
    public ShrinkableHashMap(int initialCapacity, float upperFactor) {
	hashMap = new HashMap(initialCapacity, (float)1.0);
	upperLoadFactor = upperFactor;
	capacity = initialCapacity;

	if (initialCapacity > LOWEST_SIZE)
	    LOWEST_SIZE = initialCapacity;
    }

    /**
     * Constructor: 
     * to specify the lower bound factor, it is necessary to specify
     * the upperLoad factor too so that compiler won't get confused
     */
    public ShrinkableHashMap(int initialCapacity, float upperFactor,
			       float lowerFactor)
    {
	hashMap = new HashMap(initialCapacity, (float)1.0);
	upperLoadFactor = upperFactor;
	lowerLoadFactor = lowerFactor;
	capacity = initialCapacity;

	if (initialCapacity > LOWEST_SIZE)
	    LOWEST_SIZE = initialCapacity;
    }

    //----------------------------------- other methods
    /**
     * shrink the table size by half
     */
    protected void shrinkRehash() {
	if (capacity <= LOWEST_SIZE)
	    return;                  // do nothing, it's small enough
	if (capacity % 2 != 0)       // capacity was odd
	    capacity = (capacity - 1)/2;
	else capacity = capacity / 2;// it's even

	if (capacity < LOWEST_SIZE)
	    capacity = LOWEST_SIZE;

	try {
	    hashMap = rehash();
	}
	catch (OutOfMemoryError mem_e) {
	   System.err.println("ShrinkableHashMap.shrinkRehash(): Out of memory when trying to do rehash");
	} 
    }

    /**
     * enlarge the table size by 2 (plus 1)
     */
    protected void growRehash() {
	capacity = capacity * 2 + 1;
	try {
	    hashMap = rehash();
	}
	catch (OutOfMemoryError mem_e) {
	    System.err.println("ShrinkableHashMap.growRehash(): Out of memory when trying to do rehash");
	}
    }

    /**
     * Create a new Map with given new capacity.
     */
    private HashMap rehash()
    {
	HashMap tempMap = 
	    new HashMap(capacity, (float)1.0);
	IpNetFlow flow;

	// move all the key/value pairs to the new map
	for (Iterator iter = hashMap.values().iterator(); iter.hasNext() ;) {
	    flow = (IpNetFlow)(iter.next());
	    tempMap.put(flow.makeKey(), flow);
	}
	
	return tempMap;
    }

    /**
     * Put the object with given key in the map. 
     * "grow" in size if needed
     */
    public void put(Object key, Object Item)
    {
	hashMap.put(key,Item);

	//-- need to grow?
	if (hashMap.size()/capacity >= upperLoadFactor)
	    growRehash();
    }

    /**
     * Return the object of the given key
     * notice that potentially an object list is returned
     * @ret null if none found
     */
    public Object get(Object key)
    {
	return hashMap.get(key);
    }

    /**
     * Return a collection view of all the objects stored in the map
     */
    public Collection values()
    {
	return hashMap.values();
    }
    /**
     * Remove the object corrsponding to the given key
     * if the memory utilization is low enough, shrink
     */
    public void remove(Object key)
    {
	hashMap.remove(key);

	if (hashMap.size()/capacity <= lowerLoadFactor)
	    shrinkRehash();
    }
}



